/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.commonmodel.core.util;

import com.inspur.edp.cef.api.session.ICefSession;
import com.inspur.edp.commonmodel.core.session.serviceinterface.SessionCacheInfoService;
import com.inspur.edp.commonmodel.core.session.serviceinterface.SessionConfigService;
import com.inspur.edp.commonmodel.core.session.sessioncache.SessionCacheInfoServiceImpl;
import com.inspur.edp.commonmodel.core.session.tableentity.DBConnectInfo;
import com.inspur.edp.commonmodel.core.session.tableentity.SessionCacheInfo;
import com.inspur.edp.commonmodel.core.session.tableentity.SessionConfig;
import io.iec.edp.caf.boot.context.CAFContext;
import io.iec.edp.caf.commons.dataaccess.DbType;
import io.iec.edp.caf.commons.dataaccess.JDBCConnectionInfo;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import io.iec.edp.caf.database.DatabaseManager;
import io.iec.edp.caf.databaseobject.api.entity.AbstractDatabaseObject;
import io.iec.edp.caf.databaseobject.api.entity.DBInfo;
import io.iec.edp.caf.databaseobject.api.entity.DataType;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectColumn;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectTable;
import io.iec.edp.caf.databaseobject.api.entity.DatabaseObjectType;
import io.iec.edp.caf.lock.service.api.api.DistributedLock;
import io.iec.edp.caf.lock.service.api.api.DistributedLockFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.sql.Connection;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
public class SessionIncByDbUtil implements SessionIncInterface {

    private SessionConfig config;

    public SessionIncByDbUtil(SessionConfig config, boolean isCreateTable) {
        this.config = config;
        if (isCreateTable) {
            createTable();
        }
    }

    @SneakyThrows
    private void createTable() {
        if (config.isSessionTableExist()) {
            return;
        }
        DistributedLock lock = null;
        try {
            lock = SpringBeanUtils.getBean(DistributedLockFactory.class).createLock(
                    "SessionIncByDbUtil", Duration.ofSeconds(15L));
            if (lock.isAcquired()) {
                DboTool dboTool = getDboTool();
                List<String> tableNames = getTableNames();
                List<String> needBuildTableNames = new ArrayList<>();
                for (String tableName : tableNames) {
                    if (!isTableExist(tableName, dboTool)) {
                        needBuildTableNames.add(tableName);
                    }
                }
                buildTable(needBuildTableNames, dboTool);
                updateConfig();
            } else {
                throw new RuntimeException("create table by dbo time out");
            }
        } finally {
            if (lock != null)
                lock.close();
        }
    }

    @Override
    public List<SessionCacheInfo> getSessionCacheInfos(String sessionId, int version) {
        return executeSqlByConfigConnection((dataSource, tableName) -> {
            SessionCacheInfoService service = new SessionCacheInfoServiceImpl(dataSource, tableName);
            return service.getSessionCacheInfos(sessionId, version);
        });
    }

    @Override
    public void saveSessionCache(SessionCacheInfo info, ICefSession items) {
        executeSqlByConfigConnection((dataSource, tableName) -> {
            SessionCacheInfoService service = new SessionCacheInfoServiceImpl(dataSource, tableName);
            if (info.getSessionChangeSet() != null && info.getSessionChangeSet().isReset()) {
                service.deleteSessionCacheInfos(info.getSessionId(), info.getVersion());
            }
            service.saveSessionCacheInfo(info, items.getSessionItems());
            return null;
        });
    }

    @Override
    public void clearSessionCache(String sessionId) {
        executeSqlByConfigConnection((dataSource, tableName) -> {
            SessionCacheInfoService service = new SessionCacheInfoServiceImpl(dataSource, tableName);
            service.clearSessionCacheInfos(sessionId);
            return null;
        });
    }

    @Override
    public boolean isLatestVersion(String sessionId, int version) {
        return executeSqlByConfigConnection((dataSource, tableName) -> {
            SessionCacheInfoService service = new SessionCacheInfoServiceImpl(dataSource, tableName);
            return service.isLatestVersion(sessionId, version);
        });
    }

    @Override
    public List<List<SessionCacheInfo>> traversalSessionCacheInfos(List<Date> dates) {
        List<List<SessionCacheInfo>> sessionCacheInfos = new ArrayList<>();
        List<String> tableNames = getTableNames();
        for (int i = 0; i < tableNames.size(); i++) {
            String tableName = tableNames.get(i);
            Date startDate = dates.get(i);
            executeSqlByConfigConnection(tableName, (dataSource, targetTableName) -> {
                SessionCacheInfoService service = new SessionCacheInfoServiceImpl(dataSource, targetTableName);
                List<SessionCacheInfo> temp = service.traversalSessionCacheInfos(startDate);
                sessionCacheInfos.add(temp);
                return null;
            });
        }
        return sessionCacheInfos;
    }

    private List<String> getTableNames() {
        String tableNameModel = config.getConnectInfo().getDBConnectInfo().getTableName();
        int tableCount = config.getConnectInfo().getDBConnectInfo().getTableCount();
        List<String> tableNames = new ArrayList<>();
        if (tableCount <= 1) {
            tableNames.add(tableNameModel);
        } else {
            for (int i = 0; i < tableCount; i++) {
                tableNames.add(tableNameModel + (i + 1));
            }
        }
        return tableNames;
    }

    /**
     * 批量删除
     *
     * @param sessionIdss 第一层List表示第几个表，第二层表示表中的数据
     */
    @Override
    public void batchClear(List<List<String>> sessionIdss) {
        List<String> tableNames = getTableNames();
        for (int i = 0; i < sessionIdss.size(); i++) {
            List<String> sessionIds = sessionIdss.get(i);
            String tableName = tableNames.get(i);

            if (sessionIds == null || sessionIds.isEmpty()) {
                continue;
            }

            executeSqlByConfigConnection(tableName, (dataSource, targetTableName) -> {
                SessionCacheInfoService service = new SessionCacheInfoServiceImpl(dataSource, targetTableName);
                service.batchClear(sessionIds);
                return null;
            });
        }
    }

    private boolean isTableExist(String tableCode, DboTool dboTool) {
        return dboTool.isTableExist(tableCode);
    }

    private void buildTable(List<String> tableNames, DboTool dboTool) {
        List<AbstractDatabaseObject> dbos = new ArrayList<>(tableNames.size());
        for (String tableName : tableNames) {
            DatabaseObjectTable dbo = new DatabaseObjectTable();
            dbo.setId(tableName);
            dbo.setCode(tableName);
            dbo.setName(tableName);
            dbo.setColumns(createColumns());
            dbo.setVersion("1");

            dbos.add(dbo);
        }
        dboTool.deployDatabaseObjects(dbos);
    }

    private void updateConfig() {
        SessionConfigService service = SpringBeanUtils.getBean(SessionConfigService.class);
        config.setSessionTableExist(true);
        service.updateConfigCache(config);
    }

    private List<DatabaseObjectColumn> createColumns() {

        List<DatabaseObjectColumn> colums = new ArrayList<>();

        DatabaseObjectColumn column1 = new DatabaseObjectColumn();
        column1.setCode("ID");
        column1.setName("ID");
        column1.setType(DatabaseObjectType.Column);
        column1.setTypeStr("Column");
        column1.setLength(100);
        column1.setDefaultValue("");
        column1.setDataTypeStr(DataType.Varchar);
        column1.setDataType(DataType.Varchar);
        column1.setIfPrimaryKey(true);
        column1.setNullable(false);
        column1.setUnique(true);
        colums.add(column1);

        DatabaseObjectColumn column2 = new DatabaseObjectColumn();
        column2.setCode("sessioncontent");
        column2.setName("sessioncontent");
        column2.setType(DatabaseObjectType.Column);
        column2.setTypeStr("Column");
        column2.setDefaultValue("");
        column2.setDataTypeStr(DataType.Clob);
        column2.setDataType(DataType.Clob);
        column2.setIfPrimaryKey(false);
        column2.setNullable(true);
        column2.setUnique(false);
        colums.add(column2);

        DatabaseObjectColumn column3 = new DatabaseObjectColumn();
        column3.setCode("sessionid");
        column3.setName("sessionid");
        column3.setType(DatabaseObjectType.Column);
        column3.setTypeStr("Column");
        column3.setLength(36);
        column3.setDefaultValue("");
        column3.setDataTypeStr(DataType.Varchar);
        column3.setDataType(DataType.Varchar);
        column3.setIfPrimaryKey(false);
        column3.setNullable(true);
        column3.setUnique(false);
        colums.add(column3);

        DatabaseObjectColumn column4 = new DatabaseObjectColumn();
        column4.setCode("version");
        column4.setName("version");
        column4.setType(DatabaseObjectType.Column);
        column4.setTypeStr("Column");
        column4.setLength(6);
        column4.setDefaultValue("");
        column4.setDataTypeStr(DataType.Int);
        column4.setDataType(DataType.Int);
        column4.setIfPrimaryKey(false);
        column4.setNullable(true);
        column4.setUnique(false);
        colums.add(column4);

        DatabaseObjectColumn column5 = new DatabaseObjectColumn();
        column5.setCode("createdon");
        column5.setName("createdon");
        column5.setType(DatabaseObjectType.Column);
        column5.setTypeStr("Column");
        column5.setLength(6);
        column5.setDefaultValue("");
        column5.setDataTypeStr(DataType.TimeStamp);
        column5.setDataType(DataType.TimeStamp);
        column5.setIfPrimaryKey(false);
        column5.setNullable(true);
        column5.setUnique(false);
        colums.add(column5);

        return colums;
    }

    private String getCurrentUserSessionCacheTableName() {
        String tableName;
        if (config.getConnectInfo().getDBConnectInfo().getTableCount() > 1) {
            int tableCount =
                    (Math.abs(CAFContext.current.getUserId().hashCode())) % config.getConnectInfo()
                            .getDBConnectInfo().getTableCount();
            tableCount++;
            tableName = config.getConnectInfo().getDBConnectInfo().getTableName() + tableCount;
        } else {
            tableName = config.getConnectInfo().getDBConnectInfo().getTableName();
        }
        return tableName;
    }

    private <R> R executeSqlByConfigConnection(SessionExecutor<R> function) {
        String tableName = getCurrentUserSessionCacheTableName();
        return executeSqlByConfigConnection(tableName, function);
    }

    private <R> R executeSqlByConfigConnection(String tableName, SessionExecutor<R> function) {
        DBConnectInfo dbConnectInfo = config.getConnectInfo().getDBConnectInfo();
        if (dbConnectInfo.isUseDefConnect()) {
            //如果使用默认数据库连接
            Connection connection = null;
            try {
                DatabaseManager.getInstance().begin();
                DataSource dataSource = DatabaseManager.getInstance().getDatabase().getJdbcTemplate().getDataSource();
                R result = function.execute(dataSource, tableName);
                return result;
            } catch (Throwable e) {
                throw caseRuntimeException(e);
            } finally {
                DatabaseManager.getInstance().end();
            }
        } else {
            DataSource dataSource = SessionDataSourceManager.getInstance().getDataSource(config.getId(), dbConnectInfo);
            try {
                R result = function.execute(dataSource, tableName);
                return result;
            } catch (Throwable e) {
                throw caseRuntimeException(e);
            }
        }
    }

    private DboTool getDboTool() {
        DBConnectInfo dbConnectInfo = config.getConnectInfo().getDBConnectInfo();
        if (dbConnectInfo.isUseDefConnect()) {
            //如果使用默认数据库连接
            return new DboTool();
        } else {
            JDBCConnectionInfo connectionInfo = DbConnectInfoUtil.toJDBCConnectionInfo(config.getId(), dbConnectInfo);
            DBInfo dbInfo = convertToDbInfo(connectionInfo, dbConnectInfo.getDbType());
            return new DboTool(dbInfo);
        }
    }

    private DBInfo convertToDbInfo(JDBCConnectionInfo connectionInfo, DbType dbType) {
        DBInfo dBInfo = new DBInfo();
        dBInfo.setUserName(connectionInfo.getUserName());
        dBInfo.setPassWord(connectionInfo.getPassword());
        dBInfo.setUrl(connectionInfo.getJdbcUrl());
        switch (dbType) {
            case SQLServer:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.SQLServer);
                String dbName = connectionInfo.getJdbcUrl().split("database=")[1];
                if (dbName.contains(";"))
                    dbName = dbName.split(";")[0];
                dBInfo.setDbName(dbName);
                break;
            case Oracle:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.Oracle);
                break;
            case PgSQL:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.PgSQL);
                break;
            case MySQL:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.MySQL);
                String mysqlUrl = connectionInfo.getJdbcUrl().split("[?]")[0];
                String mysqlDbName = mysqlUrl.substring(mysqlUrl.lastIndexOf("/") + 1);
                dBInfo.setDbName(mysqlDbName);
                break;
            case DM:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.DM);
                break;
            case HighGo:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.HighGo);
                break;
            case Gbase:
                throw new IllegalArgumentException("未识别的数据库类型：" + dbType);
            case Oscar:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.Oscar);
                break;
            case Kingbase:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.Kingbase);
                break;
            case DB2:
                dBInfo.setDbType(io.iec.edp.caf.databaseobject.api.entity.DbType.DB2);
                break;
            case Unknown:
                throw new IllegalArgumentException("未识别的数据库类型：" + dbType);
            default:
                throw new IllegalArgumentException("未识别的数据库类型：" + dbType);
        }
        return dBInfo;
    }

    private RuntimeException caseRuntimeException(Throwable e) {
        if (e instanceof RuntimeException) {
            return (RuntimeException) e;
        } else {
            return new RuntimeException(e);
        }
    }

    /**
     * 是否存在任意一个存储表
     */
    @Override
    public boolean isStorageInited() {
        DboTool dboTool = getDboTool();
        List<String> tableNames = getTableNames();
        for (String tableName : tableNames) {
            if (isTableExist(tableName, dboTool)) {
                return true;
            }
        }
        return false;
    }
}
